package org.budo.dubbo.serialize.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;

import org.budo.support.java.net.util.UrlEncodeUtil;
import org.budo.support.mvcs.Mvcs;
import org.springframework.util.StringUtils;

import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.serialize.ObjectInput;
import com.alibaba.dubbo.common.serialize.ObjectOutput;
import com.alibaba.dubbo.common.serialize.Serialization;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.JSONSerializer;
import com.alibaba.fastjson.serializer.SerializeWriter;
import com.alibaba.fastjson.serializer.SerializerFeature;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lmw
 */
@Slf4j
public class DubboSerializeUtil {
    private static final String UTF_8 = "UTF-8";

    private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";

    static {
        try {
            ParserConfig globalInstance = ParserConfig.getGlobalInstance();
            globalInstance.setAutoTypeSupport(true);
        } catch (Throwable e) {
            log.error("#38 check your fastjson version, e=" + e, e);
        }
    }

    // 不同版本的dubbo,包名有变化
    public static boolean isFastJsonSerialization(Serialization serialization) {
        return serialization.getClass().getSimpleName().equals("FastJsonSerialization");
    }

    /**
     * 将Json反序列化为对象
     */
    public static <T> T deserialize(Serialization serialization, byte[] bodyBytes, Class<T> resultType) {
        try {
            boolean fastJsonSerialization = isFastJsonSerialization(serialization);
            if (fastJsonSerialization) {
                return fastJsonDeserialize(serialization, bodyBytes, resultType);
            }

            // 其他序列化,正常处理
            return deserialize_0(serialization, bodyBytes, resultType);
        } catch (Throwable e) {
            throw new RuntimeException("#44 deserialize error, serialization=" + serialization //
                    + ", data=" + new String(bodyBytes)//
                    + ", headerResultType=" + resultType, e);
        }
    }

    private static <T> T deserialize_0(Serialization serialization, byte[] bodyBytes, Class<T> resultType) throws IOException, ClassNotFoundException, UnsupportedEncodingException {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bodyBytes);
        URL url = null;
        ObjectInput objectInput = serialization.deserialize(url, byteArrayInputStream);

        T resultEntity = objectInput.readObject(resultType);

        if (log.isTraceEnabled()) {
            String bodyString_1 = new String(bodyBytes); // 中文乱码特殊处理
            String bodyString_2 = new String(bodyBytes, UTF_8); // 中文乱码特殊处理
            log.trace("#47 resultEntity=" + resultEntity + ", bodyString_1=" + bodyString_1 + ", bodyString_2=" + bodyString_2 //
                    + ", bodyBytes=" + Arrays.toString(bodyBytes));
        }

        return resultEntity;
    }

    private static <T> T fastJsonDeserialize(Serialization serialization, byte[] bodyBytes, Class<T> resultType) throws UnsupportedEncodingException {
        String bodyStringUtf8 = new String(bodyBytes, UTF_8); // 中文乱码特殊处理

        if (log.isTraceEnabled()) {
            String bodyString = new String(bodyBytes); // 中文乱码特殊处理
            log.trace("#47 bodyString=" + bodyString + ", bodyStringUtf8=" + bodyStringUtf8 + ", bodyBytes=" + Arrays.toString(bodyBytes));
        }

        // 不支持 requestBody 的用 _requestBody=
        if (StringUtils.startsWithIgnoreCase(bodyStringUtf8, "_requestBody=")) {
            String requestBodyEncoded = bodyStringUtf8.substring(13);
            String requestBody = UrlEncodeUtil.decode(requestBodyEncoded);
            return JSON.parseObject(requestBody, resultType);
        }

        // 不是 json
        if (!StringUtils.startsWithIgnoreCase(bodyStringUtf8, "{") && !StringUtils.startsWithIgnoreCase(bodyStringUtf8, "[")) {
            log.warn("#51 bodyString_2=" + bodyStringUtf8 + ", not json, serialization=" + serialization //
                    + ", requestURI=" + Mvcs.getRequestURI() + ", request=" + Mvcs.getRequest());
            return null;
        }

        try {
            return JSON.parseObject(bodyStringUtf8, resultType);
        } catch (Throwable e) {
            throw new RuntimeException("#108 fastJsonDeserialize, JSON.parseObject error, bodyStringUtf8=" + bodyStringUtf8 + ", resultType=" + resultType + ", e=" + e, e);
        }
    }

    /**
     * @see com.alibaba.dubbo.common.serialize.support.json.FastJsonObjectOutput#writeObject(Object)
     */
    public static byte[] serializeResultValue(Serialization serialization, Object resultValue, Boolean useDateFormat) {
        try {
            if (isFastJsonSerialization(serialization)) {
                return fastJsonSerialize(resultValue, useDateFormat);
            }

            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutput objectOutput = serialization.serialize(null, byteArrayOutputStream);
            objectOutput.writeObject(resultValue);
            byte[] bytes = byteArrayOutputStream.toByteArray();

            if (log.isTraceEnabled()) {
                log.trace("#139 str_1=" + new String(bytes) + ", str_2=" + new String(bytes, UTF_8) + ", resultValue=" + resultValue + ", bytes=" + Arrays.toString(bytes));
            }

            return bytes;
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    private static byte[] fastJsonSerialize(Object resultValue, Boolean useDateFormat) throws UnsupportedEncodingException {
        SerializeWriter serializeWriter = new SerializeWriter();
        JSONSerializer jsonSerializer = new JSONSerializer(serializeWriter);

        jsonSerializer.config(SerializerFeature.WriteEnumUsingToString, true);
        jsonSerializer.config(SerializerFeature.DisableCircularReferenceDetect, true);

        if (null != useDateFormat && useDateFormat) {
            jsonSerializer.config(SerializerFeature.WriteDateUseDateFormat, true);
            jsonSerializer.setDateFormat(DATE_FORMAT);
        }

        jsonSerializer.write(resultValue);

        byte[] bytes = serializeWriter.toBytes(UTF_8);
        serializeWriter.close(); // for reuse SerializeWriter buf

        if (log.isTraceEnabled()) {
            log.trace("#127 str_1=" + new String(bytes) + ", str_2=" + new String(bytes, UTF_8) + ", resultValue=" + resultValue + ", bytes=" + Arrays.toString(bytes));
        }

        return bytes;
    }

    /**
     * 将对象序列化为Json
     */
    public static String serialize(Invocation invocation, Serialization serialization) throws IOException {
        URL url = invocation.getInvoker().getUrl();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutput objectOutput = serialization.serialize(url, byteArrayOutputStream);

        objectOutput.writeObject(invocation);
        byte[] bytes = byteArrayOutputStream.toByteArray();
        String str = new String(bytes, UTF_8);

        if (log.isTraceEnabled()) {
            log.trace("#74 str=" + str + ", bytes=" + Arrays.toString(bytes) + ", invocation=" + invocation + ", serialization=" + serialization);
        }

        return str;
    }
}